// 19.4 类成员指针
/**
 * 成员指针（pointer to member）是指可以指向类的非静态成员的指针。一般情况下，指针指向一个对象，但是成员指针指示的是类的成员，而非类的对象。
 *   类的静态成员不属于任何对象，因此无须特殊的指向静态成员的指针，指向静态成员的指针与普通指针没有什么区别。
 * 成员指针的类型囊括了类的类型以及成员的类型。当初始化一个这样的指针时，我们令其指向类的某个成员，但是不指定该成员所属的对象；直到使用成员指针时，才提供成员所属的对象。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"
#include <tuple>
#include <bitset>
#include <regex>
#include <random>
#include <cmath>
#include <iomanip>
#include <cstdio>
#include <cstdlib>
#include <typeinfo>

int main()
{
    // 19.4.1 数据成员指针
    /*与普通指针不同的是，成员指针还必须包含成员所属的类。因此，我们必须在＊之前添加classname：：以表示当前定义的指针可以指向classname的成员。例如：[插图]
const string Screen::*pdata; // pdata可以指向一个常量（非常量）Screen对象的string成员
      上述语句将pdata声明成“一个指向Screen类的const string成员的指针”。
      常量对象的数据成员本身也是常量，因此将我们的指针声明成指向const string成员的指针意味着pdata可以指向任何Screen对象的一个成员，而不管该Screen对象是否是常量。
        作为交换条件，我们只能使用pdata读取它所指的成员，而不能向它写入内容。
      当我们初始化一个成员指针（或者向它赋值）时，需指定它所指的成员。例如，我们可以令pdata指向某个非特定Screen对象的contents成员：[插图]
pdata = &Screen::contents;
      当然，在C++11新标准中声明成员指针最简单的方法是使用auto或decltype：[插图]
auto pdata = &Screen::contents;
    */

    // 使用数据成员指针
    /*必须清楚的一点是，当我们初始化一个成员指针或为成员指针赋值时，该指针并没有指向任何数据。成员指针指定了成员而非该成员所属的对象，只有当解引用成员指针时我们才提供对象的信息。
      与成员访问运算符.和->类似，也有两种成员指针访问运算符：.＊和->＊，这两个运算符使得我们可以解引用指针并获得该对象的成员：[插图]
Screen myScreen, *pScreen = &myScreen;
// .*解引用pdata以获得myScreen对象的contents成员
auto s = myScreen.*pdata;
// ->*解引用pdata以获得pScreen所指对象的contents成员
s = pScreen->*pdata;
    */

    // 返回数据成员指针的函数
    /*因为数据成员一般情况下是私有的，所以我们通常不能直接获得数据成员的指针。如果一个像Screen这样的类希望我们可以访问它的contents成员，最好定义一个函数，令其返回值是指向该成员的指针：[插图]
class Screen {
public:
  // data是一个静态成员，返回一个成员指针
  static const std::string Screen::*data() { return &Screen::contents; }
  // 其他成员与之前版本一致
};
      从右向左阅读函数的返回类型，可知data返回的是一个指向Screen类的const string成员的指针。函数体对contents成员使用了取地址运算符，因此函数将返回指向Screen类contents成员的指针。
      当我们调用data函数时，将得到一个成员指针：[插图]
const string Screen::*pdata = Screen::data(); // data()返回一个指向Screen类的contents成员的指针
      一如往常，pdata指向Screen类的成员而非实际数据。要想使用pdata，必须把它绑定到Screen类型的对象上：[插图]
auto s = myScreen.*pdata; // 获得myScreen对象的contents成员
    */

    // 19.4.2 成员函数指针
    /*我们也可以定义指向类的成员函数的指针。与指向数据成员的指针类似，对于我们来说要想创建一个指向成员函数的指针，最简单的方法是使用auto来推断类型：[插图]
auto pmf = &Screen::get_cursor;
      和指向数据成员的指针一样，我们使用classname：：＊的形式声明一个指向成员函数的指针。类似于任何其他函数指针（参见6.7节，第221页），指向成员函数的指针也需要指定目标函数的返回类型和形参列表。
        如果成员函数是const成员（参见7.1.2节，第231页）或者引用成员（参见13.6.3节，第483页），则我们必须将const限定符或引用限定符包含进来。
      和普通的函数指针类似，如果成员存在重载的问题，则我们必须显式地声明函数类型以明确指出我们想要使用的是哪个函数（参见6.7节，第211页）。
        例如，我们可以声明一个指针，令其指向含有两个形参的get：[插图]
char (Screen::*pmf2) (Screen::pos, Screen::pos) const;
pmf2 = &Screen::get;
        出于优先级的考虑，上述声明中Screen：：＊两端的括号必不可少。如果没有这对括号的话，编译器将认为该声明是一个（无效的）函数声明。
        和普通函数指针不同的是，在成员函数和指向该成员的指针之间不存在自动转换规则：[插图]
// pmf指向一个Screen成员，该成员不接受任何实参且返回类型是char
pmf = &Screen::get; // 必须显示地使用取地址运算符
pmf = Screen::get;  // 错误：在成员函数和指针之间不存在自动转换规则
    */

    // 使用成员函数指针
    /*和使用指向数据成员的指针一样，我们使用.＊或者->＊运算符作用于指向成员函数的指针，以调用类的成员函数：[插图]
Screen myScreen, *pScreen = &myScreen;
char c1 = (pScreen->*pmf)();      // 通过pScreen所指地对象调用pmf所指的函数
char c2 = (myScreen.*pmf2)(0, 0); // 通过myScreen对象将实参0, 0传给含有两个形参的get函数
      因为函数调用运算符的优先级较高，所以在声明指向成员函数的指针并使用这样的指针进行函数调用时，括号必不可少：（C：：＊p）（parms）和（obj.＊p）（args）。
    */

    // 使用成员指针的类型别名
    /*使用类型别名或typedef（参见2.5.1节，第60页）可以让成员指针更容易理解。例如，下面的类型别名将Action定义为两参数get函数的同义词：[插图]
using Action = char (Screen::*)(Screen::pos, Screen::pos) const;
      Action是某类型的另外一个名字，该类型是“指向Screen类的常量成员函数的指针，其中这个成员函数接受两个pos形参，返回一个char”。通过使用Action，我们可以简化指向get的指针定义：[插图]
using get = &Screen::get; // get指向Screen的get成员
      和其他函数指针类似，我们可以将指向成员函数的指针作为某个函数的返回类型或形参类型。其中，指向成员的指针形参也可以拥有默认实参：[插图]
Screen& action(Screen& screen, Action action = Screen::get);
      action是包含两个形参的函数，其中一个形参是Screen对象的引用，另一个形参是指向Screen成员函数的指针，成员函数必须接受两个pos形参并返回一个char。
        当我们调用action时，只需将Screen的一个符合要求的函数的指针或地址传入即可：[插图]
Screen myScreen;
action(myScreen);               // 使用默认实参
action(myScreen, get);          // 使用我们之前定义的变量get
action(myScreen, &Screen::get); // 显示地传入地址
      通过使用类型别名，可以令含有成员指针的代码更易读写。
    */

    // 成员指针函数表
    /*对于普通函数指针和指向成员函数的指针来说，一种常见的用法是将其存入一个函数表当中（参见14.8.3节，第511页）。如果一个类含有几个相同类型的成员，则这样一张表可以帮助我们从这些成员中选择一个。
      假定Screen类含有几个成员函数，每个函数负责将光标向指定的方向移动：[插图]
class Screen {
public:
  // 其他接口和实现成员与之前一致
  Screen& home();    // 光标移动函数
  Screen& forward();
  Screen& back();
  Screen& up();
  Screen& down();
};
      这几个新函数有一个共同点：它们都不接受任何参数，并且返回值是发生光标移动的Screen的引用。
      我们希望定义一个move函数，使其可以调用上面的任意一个函数并执行对应的操作。为了支持这个新函数，我们将在Screen中添加一个静态成员，该成员是指向光标移动函数的指针的数组：[插图][插图]
class Screen {
public:
  // 其他接口和实现成员与之前一致
  // Action是一个指针，可以用任意一个光标移动函数对其赋值
  using Action = Screen& (Screen::*)();
  // 指定具体要移动的方向，其中enum参见19.3节
  enum Directions { HOME, FORWARD, BACK, UP, DOWN };
  Screen& move(Directions);
private:
  static Action Menu[]; // 函数表
};
      数组Menu依次保存每个光标移动函数的指针，这些函数将按照Directions中枚举成员对应的偏移量存储。move函数接受一个枚举成员并调用相应的函数：[插图]
Screen& Screen::move(Directions cm) {
  // 运行this对象中索引值为cm的元素
  return (this->*Menu[cm])(); // Menu[cm]指向一个成员函数
}
      move中的函数调用的原理是：首先获取索引值为cm的Menu元素，该元素是指向Screen成员函数的指针。我们根据this所指的对象调用该元素所指的成员函数。
        当我们调用move函数时，给它传入一个表示光标移动方向的枚举成员：[插图]
Screen myScreen;
myScreen.move(Screen::HOME); // 调用myScreen.home
myScreen.move(Screen::UP);   // 调用myScreen.up
      剩下的工作就是定义并初始化函数表本身了：[插图]
Screen::Action Screen::Menu[] = { &Screen::home, &Screen::forward, &Screen::back, &Screen::up, &Screen::down };
    */

    // 19.4.3 将成员函数用作可调用对象
    /*如我们所知，要想通过一个指向成员函数的指针进行函数调用，必须首先利用.＊运算符或->＊运算符将该指针绑定到特定的对象上。
        因此与普通的函数指针不同，成员函数指针不是一个可调用对象，这样的指针不支持函数调用运算符（参见10.3.2节，第346页）。
      因为成员指针不是可调用对象，所以我们不能直接将一个指向成员函数的指针传递给算法。举个例子，如果我们想在一个string的vector中找到第一个空string，显然不能使用下面的语句：[插图]
auto fp = &string::empty;        // fp指向string的empty函数
find_if(v.begin(), v.end(), fp); // 错误：fp不是一个可调用对象，必须使用.*或->*调用成员函数指针
      find_if算法需要一个可调用对象，但我们提供给它的是一个指向成员函数的指针fp。
    */

    // 使用function生成一个可调用对象
    /*从指向成员函数的指针获取可调用对象的一种方法是使用标准库模板function（参见14.8.3节，第511页）：[插图]
function<bool (const string&)> fcn = &string::empty;
find_if(v.begin(), v.end(), fcn);
      当一个function对象包含有一个指向成员函数的指针时，function类知道它必须使用正确的指向成员的指针运算符来执行函数调用。也就是说，我们可以认为在find_if当中含有类似于如下形式的代码：[插图]
// 假设it是find_if内部的迭代器，则*it是给定范围内的一个对象
if(fcn(*it)) // 假设fcn是find_if内部一个可调用对象的名字
      其中，function将使用正确的指向成员的指针运算符。从本质上来看，function类将函数调用转换成了如下形式：[插图]
// 假设it是find_if内部的迭代器，则*it是给定范围内的一个对象
if(((*it).*p)()) // 假设p是fcn内部的一个指向成员函数的指针
      以定义fcn为例，我们想在string对象的序列上调用find_if，因此我们要求function生成一个接受string对象的可调用对象。又因为我们的vector保存的是string的指针，所以必须指定function接受指针：[插图]
vector<string*> pvec;
function<bool const (string&)> fp = &string::empty;
find_if(pvec.begin(), pvec.end(), fp); // fp接受一个指向string的指针，然后使用->*运算符调用empty
    */

    // 使用mem_fn生成一个可调用对象
    /*通过上面的介绍可知，要想使用function，我们必须提供成员的调用形式。我们也可以采取另外一种方法，通过使用标准库功能mem_fn来让编译器负责推断成员的类型。
        和function一样，mem_fn也定义在functional头文件中，并且可以从成员指针生成一个可调用对象；
        和function不同的是，mem_fn可以根据成员指针的类型推断可调用对象的类型，而无须用户显式地指定：[插图][插图]
find_if(svec.begin(), svec.end(), mem_fn(&string::empty))
      mem_fn生成的可调用对象可以通过对象调用，也可以通过指针调用：[插图]
auto f = mem_fn(&string::empty); // f接受一个string或一个string*
f(*svec.begin()); // 正确：传入一个string对象，f使用.*调用empty
f(&svec[0]);      // 正确：传入一个string的指针，f使用->*调用empty
    */

    // 使用bind生成一个可调用对象
    /*出于完整性的考虑，我们还可以使用bind（参见10.3.4节，第354页）从成员函数生成一个可调用对象：[插图]
// 选择范围中每个string，并将其bind到empty的第一个隐式实参上
auto it = find_if(svec.begin(), svec.end(), bind(&string::empty, _1));
      和function类似的地方是，当我们使用bind时，必须将函数中用于表示执行对象的隐式形参转换成显式的。
      和mem_fn类似的地方是，bind生成的可调用对象的第一个实参既可以是string的指针，也可以是string的引用：[插图]
auto f = bind(&string::empty, _1); // f接受一个string或一个string*
f(*svec.begin()); // 正确：实参是一个string对象，f使用.*调用empty
f(&svec[0]);      // 正确：实参是一个string的指针，f使用->*调用empty
    */


    return 0;
}